// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/**
 * @fileoverview Test cases for jspb's binary protocol buffer reader.
 *
 * There are two particular magic numbers that need to be pointed out -
 * 2^64-1025 is the largest number representable as both a double and an
 * unsigned 64-bit integer, and 2^63-513 is the largest number representable as
 * both a double and a signed 64-bit integer.
 *
 * Test suite is written using Jasmine -- see http://jasmine.github.io/
 *
 * @author aappleby@google.com (Austin Appleby)
 */

goog.require('goog.testing.asserts');
goog.require('jspb.BinaryConstants');
goog.require('jspb.BinaryDecoder');
goog.require('jspb.BinaryReader');
goog.require('jspb.BinaryWriter');


describe('binaryReaderTest', function () {
    /**
     * Tests the reader instance cache.
     */
    it('testInstanceCaches', /** @suppress {visibility} */ function () {
        var writer = new jspb.BinaryWriter();
        var dummyMessage = /** @type {!jspb.BinaryMessage} */({});
        writer.writeMessage(1, dummyMessage, goog.nullFunction);
        writer.writeMessage(2, dummyMessage, goog.nullFunction);

        var buffer = writer.getResultBuffer();

        // Empty the instance caches.
        jspb.BinaryReader.instanceCache_ = [];

        // Allocating and then freeing three decoders should leave us with three in
        // the cache.

        var decoder1 = jspb.BinaryDecoder.alloc();
        var decoder2 = jspb.BinaryDecoder.alloc();
        var decoder3 = jspb.BinaryDecoder.alloc();
        decoder1.free();
        decoder2.free();
        decoder3.free();

        assertEquals(3, jspb.BinaryDecoder.instanceCache_.length);
        assertEquals(0, jspb.BinaryReader.instanceCache_.length);

        // Allocating and then freeing a reader should remove one decoder from its
        // cache, but it should stay stuck to the reader afterwards since we can't
        // have a reader without a decoder.
        jspb.BinaryReader.alloc().free();

        assertEquals(2, jspb.BinaryDecoder.instanceCache_.length);
        assertEquals(1, jspb.BinaryReader.instanceCache_.length);

        // Allocating a reader should remove a reader from the cache.
        var reader = jspb.BinaryReader.alloc(buffer);

        assertEquals(2, jspb.BinaryDecoder.instanceCache_.length);
        assertEquals(0, jspb.BinaryReader.instanceCache_.length);

        // Processing the message reuses the current reader.
        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        reader.readMessage(dummyMessage, function () {
            assertEquals(0, jspb.BinaryReader.instanceCache_.length);
        });

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        reader.readMessage(dummyMessage, function () {
            assertEquals(0, jspb.BinaryReader.instanceCache_.length);
        });

        assertEquals(false, reader.nextField());

        assertEquals(2, jspb.BinaryDecoder.instanceCache_.length);
        assertEquals(0, jspb.BinaryReader.instanceCache_.length);

        // Freeing the reader should put it back into the cache.
        reader.free();

        assertEquals(2, jspb.BinaryDecoder.instanceCache_.length);
        assertEquals(1, jspb.BinaryReader.instanceCache_.length);
    });


    /**
     * @param {number} x
     * @return {number}
     */
    function truncate(x) {
        var temp = new Float32Array(1);
        temp[0] = x;
        return temp[0];
    }


    /**
     * Verifies that misuse of the reader class triggers assertions.
     */
    it('testReadErrors', /** @suppress {checkTypes|visibility} */ function () {
        // Calling readMessage on a non-delimited field should trigger an
        // assertion.
        var reader = jspb.BinaryReader.alloc([8, 1]);
        var dummyMessage = /** @type {!jspb.BinaryMessage} */({});
        reader.nextField();
        assertThrows(function () {
            reader.readMessage(dummyMessage, goog.nullFunction);
        });

        // Reading past the end of the stream should trigger an assertion.
        reader = jspb.BinaryReader.alloc([9, 1]);
        reader.nextField();
        assertThrows(function () {
            reader.readFixed64()
        });

        // Reading past the end of a submessage should trigger an assertion.
        reader = jspb.BinaryReader.alloc([10, 4, 13, 1, 1, 1]);
        reader.nextField();
        reader.readMessage(dummyMessage, function () {
            reader.nextField();
            assertThrows(function () {
                reader.readFixed32()
            });
        });

        // Skipping an invalid field should trigger an assertion.
        reader = jspb.BinaryReader.alloc([12, 1]);
        reader.nextWireType_ = 1000;
        assertThrows(function () {
            reader.skipField()
        });

        // Reading fields with the wrong wire type should assert.
        reader = jspb.BinaryReader.alloc([9, 0, 0, 0, 0, 0, 0, 0, 0]);
        reader.nextField();
        assertThrows(function () {
            reader.readInt32()
        });
        assertThrows(function () {
            reader.readInt32String()
        });
        assertThrows(function () {
            reader.readInt64()
        });
        assertThrows(function () {
            reader.readInt64String()
        });
        assertThrows(function () {
            reader.readUint32()
        });
        assertThrows(function () {
            reader.readUint32String()
        });
        assertThrows(function () {
            reader.readUint64()
        });
        assertThrows(function () {
            reader.readUint64String()
        });
        assertThrows(function () {
            reader.readSint32()
        });
        assertThrows(function () {
            reader.readBool()
        });
        assertThrows(function () {
            reader.readEnum()
        });

        reader = jspb.BinaryReader.alloc([8, 1]);
        reader.nextField();
        assertThrows(function () {
            reader.readFixed32()
        });
        assertThrows(function () {
            reader.readFixed64()
        });
        assertThrows(function () {
            reader.readSfixed32()
        });
        assertThrows(function () {
            reader.readSfixed64()
        });
        assertThrows(function () {
            reader.readFloat()
        });
        assertThrows(function () {
            reader.readDouble()
        });

        assertThrows(function () {
            reader.readString()
        });
        assertThrows(function () {
            reader.readBytes()
        });
    });


    /**
     * Tests encoding and decoding of unsigned field types.
     * @param {Function} readField
     * @param {Function} writeField
     * @param {number} epsilon
     * @param {number} upperLimit
     * @param {Function} filter
     * @private
     * @suppress {missingProperties}
     */
    var doTestUnsignedField_ = function (readField,
                                         writeField, epsilon, upperLimit, filter) {
        assertNotNull(readField);
        assertNotNull(writeField);

        var writer = new jspb.BinaryWriter();

        // Encode zero and limits.
        writeField.call(writer, 1, filter(0));
        writeField.call(writer, 2, filter(epsilon));
        writeField.call(writer, 3, filter(upperLimit));

        // Encode positive values.
        for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) {
            writeField.call(writer, 4, filter(cursor));
        }

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        // Check zero and limits.
        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        assertEquals(filter(0), readField.call(reader));

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        assertEquals(filter(epsilon), readField.call(reader));

        reader.nextField();
        assertEquals(3, reader.getFieldNumber());
        assertEquals(filter(upperLimit), readField.call(reader));

        // Check positive values.
        for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) {
            reader.nextField();
            if (4 != reader.getFieldNumber()) throw 'fail!';
            if (filter(cursor) != readField.call(reader)) throw 'fail!';
        }
    };


    /**
     * Tests encoding and decoding of signed field types.
     * @param {Function} readField
     * @param {Function} writeField
     * @param {number} epsilon
     * @param {number} lowerLimit
     * @param {number} upperLimit
     * @param {Function} filter
     * @private
     * @suppress {missingProperties}
     */
    var doTestSignedField_ = function (readField,
                                       writeField, epsilon, lowerLimit, upperLimit, filter) {
        var writer = new jspb.BinaryWriter();

        // Encode zero and limits.
        writeField.call(writer, 1, filter(lowerLimit));
        writeField.call(writer, 2, filter(-epsilon));
        writeField.call(writer, 3, filter(0));
        writeField.call(writer, 4, filter(epsilon));
        writeField.call(writer, 5, filter(upperLimit));

        var inputValues = [];

        // Encode negative values.
        for (var cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) {
            var val = filter(cursor);
            writeField.call(writer, 6, val);
            inputValues.push({
                fieldNumber: 6,
                value: val
            });
        }

        // Encode positive values.
        for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) {
            var val = filter(cursor);
            writeField.call(writer, 7, val);
            inputValues.push({
                fieldNumber: 7,
                value: val
            });
        }

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        // Check zero and limits.
        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        assertEquals(filter(lowerLimit), readField.call(reader));

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        assertEquals(filter(-epsilon), readField.call(reader));

        reader.nextField();
        assertEquals(3, reader.getFieldNumber());
        assertEquals(filter(0), readField.call(reader));

        reader.nextField();
        assertEquals(4, reader.getFieldNumber());
        assertEquals(filter(epsilon), readField.call(reader));

        reader.nextField();
        assertEquals(5, reader.getFieldNumber());
        assertEquals(filter(upperLimit), readField.call(reader));

        for (var i = 0; i < inputValues.length; i++) {
            var expected = inputValues[i];
            reader.nextField();
            assertEquals(expected.fieldNumber, reader.getFieldNumber());
            assertEquals(expected.value, readField.call(reader));
        }
    };


    /**
     * Tests fields that use varint encoding.
     */
    it('testVarintFields', function () {
        assertNotUndefined(jspb.BinaryReader.prototype.readUint32);
        assertNotUndefined(jspb.BinaryWriter.prototype.writeUint32);
        assertNotUndefined(jspb.BinaryReader.prototype.readUint64);
        assertNotUndefined(jspb.BinaryWriter.prototype.writeUint64);
        assertNotUndefined(jspb.BinaryReader.prototype.readBool);
        assertNotUndefined(jspb.BinaryWriter.prototype.writeBool);
        doTestUnsignedField_(
            jspb.BinaryReader.prototype.readUint32,
            jspb.BinaryWriter.prototype.writeUint32,
            1, Math.pow(2, 32) - 1, Math.round);

        doTestUnsignedField_(
            jspb.BinaryReader.prototype.readUint64,
            jspb.BinaryWriter.prototype.writeUint64,
            1, Math.pow(2, 64) - 1025, Math.round);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readInt32,
            jspb.BinaryWriter.prototype.writeInt32,
            1, -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readInt64,
            jspb.BinaryWriter.prototype.writeInt64,
            1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readEnum,
            jspb.BinaryWriter.prototype.writeEnum,
            1, -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round);

        doTestUnsignedField_(
            jspb.BinaryReader.prototype.readBool,
            jspb.BinaryWriter.prototype.writeBool,
            1, 1, function (x) {
                return !!x;
            });
    });


    /**
     * Tests reading a field from hexadecimal string (format: '08 BE EF').
     * @param {Function} readField
     * @param {number} expected
     * @param {string} hexString
     */
    function doTestHexStringVarint_(readField, expected, hexString) {
        var bytesCount = (hexString.length + 1) / 3;
        var bytes = new Uint8Array(bytesCount);
        for (var i = 0; i < bytesCount; i++) {
            bytes[i] = parseInt(hexString.substring(i * 3, i * 3 + 2), 16);
        }
        var reader = jspb.BinaryReader.alloc(bytes);
        reader.nextField();
        assertEquals(expected, readField.call(reader));
    }


    /**
     * Tests non-canonical redundant varint decoding.
     */
    it('testRedundantVarintFields', function () {
        assertNotNull(jspb.BinaryReader.prototype.readUint32);
        assertNotNull(jspb.BinaryReader.prototype.readUint64);
        assertNotNull(jspb.BinaryReader.prototype.readSint32);
        assertNotNull(jspb.BinaryReader.prototype.readSint64);

        // uint32 and sint32 take no more than 5 bytes
        // 08 - field prefix (type = 0 means varint)
        doTestHexStringVarint_(
            jspb.BinaryReader.prototype.readUint32,
            12, '08 8C 80 80 80 00');

        // 11 stands for -6 in zigzag encoding
        doTestHexStringVarint_(
            jspb.BinaryReader.prototype.readSint32,
            -6, '08 8B 80 80 80 00');

        // uint64 and sint64 take no more than 10 bytes
        // 08 - field prefix (type = 0 means varint)
        doTestHexStringVarint_(
            jspb.BinaryReader.prototype.readUint64,
            12, '08 8C 80 80 80 80 80 80 80 80 00');

        // 11 stands for -6 in zigzag encoding
        doTestHexStringVarint_(
            jspb.BinaryReader.prototype.readSint64,
            -6, '08 8B 80 80 80 80 80 80 80 80 00');
    });


    /**
     * Tests 64-bit fields that are handled as strings.
     */
    it('testStringInt64Fields', function () {
        var writer = new jspb.BinaryWriter();

        var testSignedData = [
            '2730538252207801776',
            '-2688470994844604560',
            '3398529779486536359',
            '3568577411627971000',
            '272477188847484900',
            '-6649058714086158188',
            '-7695254765712060806',
            '-4525541438037104029',
            '-4993706538836508568',
            '4990160321893729138'
        ];
        var testUnsignedData = [
            '7822732630241694882',
            '6753602971916687352',
            '2399935075244442116',
            '8724292567325338867',
            '16948784802625696584',
            '4136275908516066934',
            '3575388346793700364',
            '5167142028379259461',
            '1557573948689737699',
            '17100725280812548567'
        ];

        for (var i = 0; i < testSignedData.length; i++) {
            writer.writeInt64String(2 * i + 1, testSignedData[i]);
            writer.writeUint64String(2 * i + 2, testUnsignedData[i]);
        }

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        for (var i = 0; i < testSignedData.length; i++) {
            reader.nextField();
            assertEquals(2 * i + 1, reader.getFieldNumber());
            assertEquals(testSignedData[i], reader.readInt64String());
            reader.nextField();
            assertEquals(2 * i + 2, reader.getFieldNumber());
            assertEquals(testUnsignedData[i], reader.readUint64String());
        }
    });


    /**
     * Tests fields that use zigzag encoding.
     */
    it('testZigzagFields', function () {
        doTestSignedField_(
            jspb.BinaryReader.prototype.readSint32,
            jspb.BinaryWriter.prototype.writeSint32,
            1, -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readSint64,
            jspb.BinaryWriter.prototype.writeSint64,
            1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round);
    });


    /**
     * Tests fields that use fixed-length encoding.
     */
    it('testFixedFields', function () {
        doTestUnsignedField_(
            jspb.BinaryReader.prototype.readFixed32,
            jspb.BinaryWriter.prototype.writeFixed32,
            1, Math.pow(2, 32) - 1, Math.round);

        doTestUnsignedField_(
            jspb.BinaryReader.prototype.readFixed64,
            jspb.BinaryWriter.prototype.writeFixed64,
            1, Math.pow(2, 64) - 1025, Math.round);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readSfixed32,
            jspb.BinaryWriter.prototype.writeSfixed32,
            1, -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readSfixed64,
            jspb.BinaryWriter.prototype.writeSfixed64,
            1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round);
    });


    /**
     * Tests floating point fields.
     */
    it('testFloatFields', function () {
        doTestSignedField_(
            jspb.BinaryReader.prototype.readFloat,
            jspb.BinaryWriter.prototype.writeFloat,
            jspb.BinaryConstants.FLOAT32_MIN,
            -jspb.BinaryConstants.FLOAT32_MAX,
            jspb.BinaryConstants.FLOAT32_MAX,
            truncate);

        doTestSignedField_(
            jspb.BinaryReader.prototype.readDouble,
            jspb.BinaryWriter.prototype.writeDouble,
            jspb.BinaryConstants.FLOAT64_EPS * 10,
            -jspb.BinaryConstants.FLOAT64_MIN,
            jspb.BinaryConstants.FLOAT64_MIN,
            function (x) {
                return x;
            });
    });


    /**
     * Tests length-delimited string fields.
     */
    it('testStringFields', function () {
        var s1 = 'The quick brown fox jumps over the lazy dog.';
        var s2 = '人人生而自由，在尊嚴和權利上一律平等。';

        var writer = new jspb.BinaryWriter();

        writer.writeString(1, s1);
        writer.writeString(2, s2);

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        assertEquals(s1, reader.readString());

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        assertEquals(s2, reader.readString());
    });


    /**
     * Tests length-delimited byte fields.
     */
    it('testByteFields', function () {
        var message = [];
        var lowerLimit = 1;
        var upperLimit = 256;
        var scale = 1.1;

        var writer = new jspb.BinaryWriter();

        for (var cursor = lowerLimit; cursor < upperLimit; cursor *= 1.1) {
            var len = Math.round(cursor);
            var bytes = [];
            for (var i = 0; i < len; i++) bytes.push(i % 256);

            writer.writeBytes(len, bytes);
        }

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        for (var cursor = lowerLimit; reader.nextField(); cursor *= 1.1) {
            var len = Math.round(cursor);
            if (len != reader.getFieldNumber()) throw 'fail!';

            var bytes = reader.readBytes();
            if (len != bytes.length) throw 'fail!';
            for (var i = 0; i < bytes.length; i++) {
                if (i % 256 != bytes[i]) throw 'fail!';
            }
        }
    });


    /**
     * Tests nested messages.
     */
    it('testNesting', function () {
        var writer = new jspb.BinaryWriter();
        var dummyMessage = /** @type {!jspb.BinaryMessage} */({});

        writer.writeInt32(1, 100);

        // Add one message with 3 int fields.
        writer.writeMessage(2, dummyMessage, function () {
            writer.writeInt32(3, 300);
            writer.writeInt32(4, 400);
            writer.writeInt32(5, 500);
        });

        // Add one empty message.
        writer.writeMessage(6, dummyMessage, goog.nullFunction);

        writer.writeInt32(7, 700);

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        // Validate outermost message.

        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        assertEquals(100, reader.readInt32());

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        reader.readMessage(dummyMessage, function () {
            // Validate embedded message 1.
            reader.nextField();
            assertEquals(3, reader.getFieldNumber());
            assertEquals(300, reader.readInt32());

            reader.nextField();
            assertEquals(4, reader.getFieldNumber());
            assertEquals(400, reader.readInt32());

            reader.nextField();
            assertEquals(5, reader.getFieldNumber());
            assertEquals(500, reader.readInt32());

            assertEquals(false, reader.nextField());
        });

        reader.nextField();
        assertEquals(6, reader.getFieldNumber());
        reader.readMessage(dummyMessage, function () {
            // Validate embedded message 2.

            assertEquals(false, reader.nextField());
        });

        reader.nextField();
        assertEquals(7, reader.getFieldNumber());
        assertEquals(700, reader.readInt32());

        assertEquals(false, reader.nextField());
    });

    /**
     * Tests skipping fields of each type by interleaving them with sentinel
     * values and skipping everything that's not a sentinel.
     */
    it('testSkipField', function () {
        var writer = new jspb.BinaryWriter();

        var sentinel = 123456789;

        // Write varint fields of different sizes.
        writer.writeInt32(1, sentinel);
        writer.writeInt32(1, 1);
        writer.writeInt32(1, 1000);
        writer.writeInt32(1, 1000000);
        writer.writeInt32(1, 1000000000);

        // Write fixed 64-bit encoded fields.
        writer.writeInt32(2, sentinel);
        writer.writeDouble(2, 1);
        writer.writeFixed64(2, 1);
        writer.writeSfixed64(2, 1);

        // Write fixed 32-bit encoded fields.
        writer.writeInt32(3, sentinel);
        writer.writeFloat(3, 1);
        writer.writeFixed32(3, 1);
        writer.writeSfixed32(3, 1);

        // Write delimited fields.
        writer.writeInt32(4, sentinel);
        writer.writeBytes(4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
        writer.writeString(4, 'The quick brown fox jumps over the lazy dog');

        // Write a group with a nested group inside.
        writer.writeInt32(5, sentinel);
        var dummyMessage = /** @type {!jspb.BinaryMessage} */({});
        writer.writeGroup(5, dummyMessage, function () {
            writer.writeInt64(42, 42);
            writer.writeGroup(6, dummyMessage, function () {
                writer.writeInt64(84, 42);
            });
        });

        // Write final sentinel.
        writer.writeInt32(6, sentinel);

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        function skip(field, count) {
            for (var i = 0; i < count; i++) {
                reader.nextField();
                if (field != reader.getFieldNumber()) throw 'fail!';
                reader.skipField();
            }
        }

        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        assertEquals(sentinel, reader.readInt32());
        skip(1, 4);

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        assertEquals(sentinel, reader.readInt32());
        skip(2, 3);

        reader.nextField();
        assertEquals(3, reader.getFieldNumber());
        assertEquals(sentinel, reader.readInt32());
        skip(3, 3);

        reader.nextField();
        assertEquals(4, reader.getFieldNumber());
        assertEquals(sentinel, reader.readInt32());
        skip(4, 2);

        reader.nextField();
        assertEquals(5, reader.getFieldNumber());
        assertEquals(sentinel, reader.readInt32());
        skip(5, 1);

        reader.nextField();
        assertEquals(6, reader.getFieldNumber());
        assertEquals(sentinel, reader.readInt32());
    });


    /**
     * Tests packed fields.
     */
    it('testPackedFields', function () {
        var writer = new jspb.BinaryWriter();

        var sentinel = 123456789;

        var unsignedData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        var signedData = [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10];
        var floatData = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10];
        var doubleData = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10];
        var boolData = [true, false, true, true, false, false, true, false];

        for (var i = 0; i < floatData.length; i++) {
            floatData[i] = truncate(floatData[i]);
        }

        writer.writeInt32(1, sentinel);

        writer.writePackedInt32(2, signedData);
        writer.writePackedInt64(2, signedData);
        writer.writePackedUint32(2, unsignedData);
        writer.writePackedUint64(2, unsignedData);
        writer.writePackedSint32(2, signedData);
        writer.writePackedSint64(2, signedData);
        writer.writePackedFixed32(2, unsignedData);
        writer.writePackedFixed64(2, unsignedData);
        writer.writePackedSfixed32(2, signedData);
        writer.writePackedSfixed64(2, signedData);
        writer.writePackedFloat(2, floatData);
        writer.writePackedDouble(2, doubleData);
        writer.writePackedBool(2, boolData);
        writer.writePackedEnum(2, unsignedData);

        writer.writeInt32(3, sentinel);

        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        reader.nextField();
        assertEquals(sentinel, reader.readInt32());

        reader.nextField();
        assertElementsEquals(reader.readPackedInt32(), signedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedInt64(), signedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedUint32(), unsignedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedUint64(), unsignedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedSint32(), signedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedSint64(), signedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedFixed32(), unsignedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedFixed64(), unsignedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedSfixed32(), signedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedSfixed64(), signedData);

        reader.nextField();
        assertElementsEquals(reader.readPackedFloat(), floatData);

        reader.nextField();
        assertElementsEquals(reader.readPackedDouble(), doubleData);

        reader.nextField();
        assertElementsEquals(reader.readPackedBool(), boolData);

        reader.nextField();
        assertElementsEquals(reader.readPackedEnum(), unsignedData);

        reader.nextField();
        assertEquals(sentinel, reader.readInt32());
    });


    /**
     * Byte blobs inside nested messages should always have their byte offset set
     * relative to the start of the outermost blob, not the start of their parent
     * blob.
     */
    it('testNestedBlobs', function () {
        // Create a proto consisting of two nested messages, with the inner one
        // containing a blob of bytes.

        var fieldTag = (1 << 3) | jspb.BinaryConstants.WireType.DELIMITED;
        var blob = [1, 2, 3, 4, 5];
        var writer = new jspb.BinaryWriter();
        var dummyMessage = /** @type {!jspb.BinaryMessage} */({});

        writer.writeMessage(1, dummyMessage, function () {
            writer.writeMessage(1, dummyMessage, function () {
                writer.writeBytes(1, blob);
            });
        });

        // Peel off the outer two message layers. Each layer should have two bytes
        // of overhead, one for the field tag and one for the length of the inner
        // blob.

        var decoder1 = new jspb.BinaryDecoder(writer.getResultBuffer());
        assertEquals(fieldTag, decoder1.readUnsignedVarint32());
        assertEquals(blob.length + 4, decoder1.readUnsignedVarint32());

        var decoder2 = new jspb.BinaryDecoder(decoder1.readBytes(blob.length + 4));
        assertEquals(fieldTag, decoder2.readUnsignedVarint32());
        assertEquals(blob.length + 2, decoder2.readUnsignedVarint32());

        assertEquals(fieldTag, decoder2.readUnsignedVarint32());
        assertEquals(blob.length, decoder2.readUnsignedVarint32());
        var bytes = decoder2.readBytes(blob.length);

        assertElementsEquals(bytes, blob);
    });


    /**
     * Tests read callbacks.
     */
    it('testReadCallbacks', function () {
        var writer = new jspb.BinaryWriter();
        var dummyMessage = /** @type {!jspb.BinaryMessage} */({});

        // Add an int, a submessage, and another int.
        writer.writeInt32(1, 100);

        writer.writeMessage(2, dummyMessage, function () {
            writer.writeInt32(3, 300);
            writer.writeInt32(4, 400);
            writer.writeInt32(5, 500);
        });

        writer.writeInt32(7, 700);

        // Create the reader and register a custom read callback.
        var reader = jspb.BinaryReader.alloc(writer.getResultBuffer());

        /**
         * @param {!jspb.BinaryReader} reader
         * @return {*}
         */
        function readCallback(reader) {
            reader.nextField();
            assertEquals(3, reader.getFieldNumber());
            assertEquals(300, reader.readInt32());

            reader.nextField();
            assertEquals(4, reader.getFieldNumber());
            assertEquals(400, reader.readInt32());

            reader.nextField();
            assertEquals(5, reader.getFieldNumber());
            assertEquals(500, reader.readInt32());

            assertEquals(false, reader.nextField());
        };

        reader.registerReadCallback('readCallback', readCallback);

        // Read the container message.
        reader.nextField();
        assertEquals(1, reader.getFieldNumber());
        assertEquals(100, reader.readInt32());

        reader.nextField();
        assertEquals(2, reader.getFieldNumber());
        reader.readMessage(dummyMessage, function () {
            // Decode the embedded message using the registered callback.
            reader.runReadCallback('readCallback');
        });

        reader.nextField();
        assertEquals(7, reader.getFieldNumber());
        assertEquals(700, reader.readInt32());

        assertEquals(false, reader.nextField());
    });
});
